﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CutAways.Geometry;
using CutAways.Modeling;
using Tao.OpenGl;

namespace CutAways.Illustration
{
	[Serializable]
	class OrientedBoundingBox : BoundingBox
	{
		private Vector[] points; // absolutni pozice obalky
		
		// ---------------------------------------------------------

		public Vector[] Points
		{
			get { return points; }
			set { points = value; }
		}

		public Quad Front
		{
			get { return new Quad(points[2], points[3], points[7], points[6]); }
		}

		public Quad Back
		{
			get { return new Quad(points[0], points[1], points[5], points[4]); }
		}

		public Quad Top
		{
			get { return new Quad(points[4], points[5], points[7], points[6]); }
		}

		public Quad Bottom
		{
			get { return new Quad(points[0], points[1], points[3], points[2]); }
		}

		public Quad Left
		{
			get { return new Quad(points[0], points[2], points[6], points[4]); }
		}

		public Quad Right
		{
			get { return new Quad(points[1], points[3], points[7], points[5]); }
		}

		public OrientedBoundingBox()
		{
			this.points = new Vector[8];
		}

		public override Vector Center
		{
			get { return Vector.CenterOfLink(points[0], points[7]); }
		}

		public override float Radius
		{
			get { return (points[0] - Center).GetLength(); }
		}

		public override void Draw()
		{
			Gl.glDisable(Gl.GL_LIGHTING);
			Gl.glBegin(Gl.GL_LINES);
			{
				Gl.glVertex3f(points[0].X, points[0].Y, points[0].Z);
				Gl.glVertex3f(points[1].X, points[1].Y, points[1].Z);

				Gl.glVertex3f(points[0].X, points[0].Y, points[0].Z);
				Gl.glVertex3f(points[4].X, points[4].Y, points[4].Z);

				Gl.glVertex3f(points[0].X, points[0].Y, points[0].Z);
				Gl.glVertex3f(points[2].X, points[2].Y, points[2].Z);

				Gl.glVertex3f(points[5].X, points[5].Y, points[5].Z);
				Gl.glVertex3f(points[4].X, points[4].Y, points[4].Z);

				Gl.glVertex3f(points[5].X, points[5].Y, points[5].Z);
				Gl.glVertex3f(points[1].X, points[1].Y, points[1].Z);

				Gl.glVertex3f(points[5].X, points[5].Y, points[5].Z);
				Gl.glVertex3f(points[7].X, points[7].Y, points[7].Z);

				Gl.glVertex3f(points[3].X, points[3].Y, points[3].Z);
				Gl.glVertex3f(points[7].X, points[7].Y, points[7].Z);

				Gl.glVertex3f(points[3].X, points[3].Y, points[3].Z);
				Gl.glVertex3f(points[2].X, points[2].Y, points[2].Z);

				Gl.glVertex3f(points[3].X, points[3].Y, points[3].Z);
				Gl.glVertex3f(points[1].X, points[1].Y, points[1].Z);

				Gl.glVertex3f(points[6].X, points[6].Y, points[6].Z);
				Gl.glVertex3f(points[4].X, points[4].Y, points[4].Z);

				Gl.glVertex3f(points[6].X, points[6].Y, points[6].Z);
				Gl.glVertex3f(points[7].X, points[7].Y, points[7].Z);

				Gl.glVertex3f(points[6].X, points[6].Y, points[6].Z);
				Gl.glVertex3f(points[2].X, points[2].Y, points[2].Z);
			}
			Gl.glEnd();
			Gl.glEnable(Gl.GL_LIGHTING);
		}

		public static OrientedBoundingBox ConvertFrom(AxesBoundingBox abb, Vector center)
		{
			OrientedBoundingBox obb = new OrientedBoundingBox();

			obb.points[0] = abb.Min;
			obb.points[7] = abb.Max;

			obb.points[1] = new Vector(abb.Max.X, abb.Min.Y, abb.Min.Z);
			obb.points[2] = new Vector(abb.Min.X, abb.Min.Y, abb.Max.Z);
			obb.points[3] = new Vector(abb.Max.X, abb.Min.Y, abb.Max.Z);
			obb.points[4] = new Vector(abb.Min.X, abb.Max.Y, abb.Min.Z);
			obb.points[5] = new Vector(abb.Max.X, abb.Max.Y, abb.Min.Z);
			obb.points[6] = new Vector(abb.Min.X, abb.Max.Y, abb.Max.Z);
			
			return obb;
		}

		public void Rotate(Vector centerOfRotations, Vector rotationAngles)
		{
			for (int i = 0; i < this.points.Length; i++)
				this.points[i] = Vector.RotateAllAxis(this.points[i] - centerOfRotations, rotationAngles) + centerOfRotations;
		}


		public Interval[] IntersectionParametres(OrientedBoundingBox obb, Vector viewpoint)
		{
			Interval parU = Interval.Empty;
			Interval parV = Interval.Empty;
			Interval parW = Interval.Empty;

			Interval u, v, w;

			// nejdriv otestuju, jestli dany objekt neni uvnitr tohoto objektu, pokud ano tak nastavim prislusne parametry
			if (this.contains(obb, out u, out v, out w))
			{
				parU.Join(u);
				parV.Join(v);
				parW.Join(w);
			}

			// projdu vsechny plochy a nastavim podle pruseciku prislusne parametry
			if (!Front.IsCollapsed())
			{
				Front.IntersectionWith(obb, viewpoint, out u, out v);
				parU.Join(u);
				parV.Join(v);
				if (u.HasIntersectionWith(Interval.ZeroOne) && v.HasIntersectionWith(Interval.ZeroOne))
					parW.Join(1f);
			}
			if (!Back.IsCollapsed())
			{
				Back.IntersectionWith(obb, viewpoint, out u, out v);
				parU.Join(u);
				parV.Join(v);
				if (u.HasIntersectionWith(Interval.ZeroOne) && v.HasIntersectionWith(Interval.ZeroOne))
					parW.Join(0f);
			}
			if (!Left.IsCollapsed())
			{
				Left.IntersectionWith(obb, viewpoint, out w, out v);
				parW.Join(w);
				parV.Join(v);
				if (w.HasIntersectionWith(Interval.ZeroOne) && v.HasIntersectionWith(Interval.ZeroOne))
					parU.Join(0f);
			}
			if (!Right.IsCollapsed())
			{
				Right.IntersectionWith(obb, viewpoint, out w, out v);
				parW.Join(w);
				parV.Join(v);
				if (w.HasIntersectionWith(Interval.ZeroOne) && v.HasIntersectionWith(Interval.ZeroOne))
					parU.Join(1f);
			}
			if (!Bottom.IsCollapsed())
			{
				Bottom.IntersectionWith(obb, viewpoint, out u, out w);
				parU.Join(u);
				parW.Join(w);
				if (u.HasIntersectionWith(Interval.ZeroOne) && w.HasIntersectionWith(Interval.ZeroOne))
					parV.Join(0f);
			}
			if (!Top.IsCollapsed())
			{
				Top.IntersectionWith(obb, viewpoint, out u, out w);
				parU.Join(u);
				parW.Join(w);
				if (u.HasIntersectionWith(Interval.ZeroOne) && w.HasIntersectionWith(Interval.ZeroOne))
					parV.Join(1f);
			}

			// ------------------------------------------------------------------------------

			parU = parU.IntersectionWith(Interval.ZeroOne);
			parV = parV.IntersectionWith(Interval.ZeroOne);
			parW = parW.IntersectionWith(Interval.ZeroOne);

			if (parU == Interval.Empty)
				parU = Interval.ZeroZero;
			if (parV == Interval.Empty)
				parV = Interval.ZeroZero;
			if (parW == Interval.Empty)
				parW = Interval.ZeroZero;
			
			return new Interval[] { parU, parV, parW };
		}

		private bool contains(OrientedBoundingBox obb, out Interval u, out Interval v, out Interval w)
		{
			u = v = w = Interval.Empty;
			Vector nU = this.points[1] - this.points[0];
			Vector nV = this.points[4] - this.points[0];
			Vector nW = this.points[2] - this.points[0];
			float nULength = nU.GetLength();
			float nVLength = nV.GetLength();
			float nWLength = nW.GetLength();
			if (nULength == 0f || nVLength == 0f || nWLength == 0f)
				return false;
			bool hit = false;
			foreach (Vector point in obb.points)
			{
				Vector x = point - this.points[0];
				float pU = Vector.Dot(x, nU) / (nULength * nULength);
				float pV = Vector.Dot(x, nV) / (nVLength * nVLength);
				float pW = Vector.Dot(x, nW) / (nWLength * nWLength);
				if (Interval.ZeroOne.Contains(pU) && Interval.ZeroOne.Contains(pV) && Interval.ZeroOne.Contains(pW))
				{
					u.Join(pU); v.Join(pV); w.Join(pW);
					hit = true;
				}
			}
			return hit;
		}
	}
}
